package com.redxun.filter;

import com.alibaba.fastjson.JSONObject;
import com.redxun.core.api.sys.ICoreSystemService;
import com.redxun.core.boot.util.AppTokenUtil;
import com.redxun.core.cache.CacheUtil;
import com.redxun.core.common.constant.SecurityConstants;
import com.redxun.core.common.entity.JsonResult;
import com.redxun.core.common.utils.I18nUtil;
import com.redxun.core.common.utils.SpringUtil;
import com.redxun.core.dto.sys.SysAppAuthDto;
import com.redxun.core.dto.sys.SysAppLogDto;
import com.redxun.core.dto.sys.SysAuthManagerDto;
import com.redxun.core.log.mq.ApiLogProducer;
import com.redxun.core.tool.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.reactive.ServerHttpRequest;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * ApiTokenFilter.
 *
 * @author aj
 */
@Slf4j
public class ApiTokenFilter implements Filter {
    
    private static final String URL_PATTERN = "/restapi/";
    
    /**
     * 缓存外部API接口授权记录的Key前缀.
     */
    private static final String APP_AUTH_KEY_PREFIX = "sys_app_auth_";
    
    private static final String APP_AUTH_REGION = "api_auth";
    
    private static final String START_TIME = "url_request_start_time";
    
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {
        servletRequest.setAttribute(START_TIME, System.currentTimeMillis());
        HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
        
        String reqUrl = httpRequest.getRequestURI().toString().toLowerCase();
        if (reqUrl.indexOf(URL_PATTERN) < 0) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        String method = httpRequest.getMethod();
        String token = "";
        Enumeration<String> lstToken = httpRequest.getHeaders("token");
        while (lstToken.hasMoreElements()) {
            String value = lstToken.nextElement();
            if (StringUtils.isNotEmpty(value)) {
                token = value;
            }
        }
        
        String appId = "local";
        //token存在的情况
        if (StringUtils.isNotEmpty(token)) {
            boolean rtn = AppTokenUtil.validToken(token);
            JsonResult result = new JsonResult();
            if (!rtn) {
                result.setSuccess(false);
                result.setData("token已过期或非法");
                result.setCode(403);
                httpResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                httpResponse.getOutputStream().close();
                return;
            }
            
            //验证url权限和方法
            appId = AppTokenUtil.getAppId(token);
            JsonResult validResult = validAuth(appId, reqUrl, method);
            //不合法
            if (!validResult.getSuccess()) {
                httpResponse.getOutputStream().write(validResult.toString().getBytes(StandardCharsets.UTF_8));
                httpResponse.getOutputStream().close();
                return;
            }
    
            //验证黑白名单。
            validHost(httpRequest, httpResponse, appId);
            
            String applicationName = SpringUtil.getApplicationContext().getEnvironment().getProperty("spring.application.name");
            String[] strArray = reqUrl.split("/");
            final int constInt = 2;
            if (strArray != null && strArray.length > constInt) {
                applicationName = "jpaas-" + strArray[2];
            }
            SysAppLogDto sysAppLogDto = new SysAppLogDto();
            sysAppLogDto.setAppId(appId);
            sysAppLogDto.setMethod(method);
            sysAppLogDto.setAppName(applicationName);
            sysAppLogDto.setUrl(httpRequest.getRequestURL().toString());
            Long startTime = (Long) servletRequest.getAttribute(START_TIME);
            
            long executeTime = (System.currentTimeMillis() - startTime);
            sysAppLogDto.setDuration((int) executeTime);
            
            ApiLogProducer apiLogProducer = SpringUtil.getBean(ApiLogProducer.class);
            apiLogProducer.send(JSONObject.toJSONString(sysAppLogDto));
            
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        //用户通过本地调用接口.
        else {
            String authorization = "";
            Enumeration<String> auths = httpRequest.getHeaders(SecurityConstants.AUTHORIZATION);
            while (auths.hasMoreElements()) {
                String value = auths.nextElement();
                if (StringUtils.isNotEmpty(value)) {
                    authorization = value;
                }
            }
            JsonResult result = new JsonResult();
            if (auths == null || StringUtils.isEmpty(authorization)) {
                result.setSuccess(false);
                result.setCode(500);
                result.setMessage("没有传入TOKEN");
                httpResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                httpResponse.getOutputStream().close();
            }
            String[] tokens = authorization.split("Bearer ");
            token = tokens[1];
            if (StringUtils.isEmpty(token)) {
                result.setSuccess(false);
                result.setCode(500);
                result.setMessage("没有传入TOKEN");
                httpResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                httpResponse.getOutputStream().close();
            } else {
                Boolean rtn = AppTokenUtil.validToken(token);
                if (!rtn) {
                    result.setSuccess(false);
                    result.setCode(500);
                    result.setMessage("没有传入TOKEN或过期");
                    httpResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                    httpResponse.getOutputStream().close();
                }
            }
        }
        filterChain.doFilter(servletRequest, servletResponse);
        return;
    }
    
    /**
     * 验证远程访问主机是否合法.
     * @param httpServletRequest  httpServletRequest
     * @param httpServletResponse httpServletResponse
     * @param appId               appId
     */
    private void validHost(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String appId)
            throws IOException {
        String host = getRemotehost(httpServletRequest);
        ICoreSystemService systemClient = SpringUtil.getBean(ICoreSystemService.class);
        SysAuthManagerDto authManagerResult = systemClient.getSysAuthManagerById(appId);
        String ipOrDomainWhite = authManagerResult.getIpOrDomainWhite();
        String ipOrDomainBlack = authManagerResult.getIpOrDomainBlack();
        //黑名单验证
        if (StringUtils.isNotEmpty(ipOrDomainBlack)) {
            String[] blackArray = ipOrDomainBlack.split(",");
            for (String black : blackArray) {
                if (black.equals(host)) {
                    JsonResult result = new JsonResult();
                    result.setSuccess(false);
                    result.setCode(500);
                    result.setMessage(I18nUtil.i18n("ipProhibitsAccess", "该IP禁止访问"));
                    httpServletResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                    httpServletResponse.getOutputStream().close();
                }
            }
        }
        //白名单验证
        if (StringUtils.isNotEmpty(ipOrDomainWhite)) {
            String[] whiteArray = ipOrDomainWhite.split(",");
            Boolean whiteFlag = false;
            for (String white : whiteArray) {
                //检查host是否在白名单中
                if (white.equals(host)) {
                    whiteFlag = true;
                }
            }
            //白名单中没有host，直接拦截
            if (!whiteFlag) {
                JsonResult result = new JsonResult();
                result.setSuccess(false);
                result.setCode(500);
                result.setMessage(I18nUtil.i18n("ipNoAlowedAccess", "该IP未被允许访问"));
                httpServletResponse.getOutputStream().write(result.toString().getBytes(StandardCharsets.UTF_8));
                httpServletResponse.getOutputStream().close();
            }
        }
    }
    
    public String getRemotehost(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip)) {
            //获取不到X-Forwarded-For,直接给一个无效的地址
            log.info(I18nUtil.i18n("addXforwardedFor", "请使用浏览器访问，或者手动加上X-Forwarded-For请求头"));
            ip = "------------";
        }
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
    }
    
    private JsonResult validAuth(String appId, String requestUrl, String method) {
        JsonResult result = new JsonResult();
        List<SysAppAuthDto> list = getSysAppAuth(appId);
        for (SysAppAuthDto sysAppAuthDto : list) {
            Boolean containUrl = sysAppAuthDto.getUrl().toLowerCase().indexOf(requestUrl) != -1;
            //地址有授权
            if (containUrl) {
                String methodName = sysAppAuthDto.getMethod();
                //请求不合法
                if (!method.equals(sysAppAuthDto.getMethod())) {
                    result.setSuccess(false);
                    result.setCode(405);
                    result.setMessage("请求方法不正确，请使用[" + methodName + "]请求");
                } else {
                    result.setSuccess(true);
                    break;
                }
            } else {
                result.setSuccess(false);
                result.setData("此请求没有被授权");
                result.setCode(405);
            }
        }
        return result;
    }

    /**
     * @param appId 客户端ID
     * @Description: 获取sys_app_auth 表中的接口授权信息
     * @Author: Elwin ZHANG  @Date: 2021/8/24 10:57
     **/
    private List<SysAppAuthDto> getSysAppAuth(String appId) {
        //先尝试从缓存中获取
        String key = APP_AUTH_KEY_PREFIX + appId;
        Object object = CacheUtil.get(APP_AUTH_REGION, key);
        if (object != null) {
            try {
                return (List<SysAppAuthDto>) object;
            } catch (Exception e) {
                log.warn(e.getMessage(), e);
            }
        }
        
        ICoreSystemService systemClient = SpringUtil.getBean(ICoreSystemService.class);
        
        //缓存中不存在，则调用Feign接口获取
        List<SysAppAuthDto> list = systemClient.findListByAppId(appId);
        CacheUtil.set(APP_AUTH_REGION, key, list);
        return list;
    }
    
}
